package block

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/bolt"
	"log"
	"os"
)

const dbFile = "db/blockchian.db" //定义数据文件名
const blocksBucket = "blocks"     //区块桶
//创世块data
const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"

// 区块链
type Blockchain struct {
	tip []byte
	db  *bolt.DB
}

// 区块链迭代器用于迭代区块
type BlockchainIterator struct {
	currentHash []byte   //当前的hash
	db          *bolt.DB //数据库
}

func (bc *Blockchain) GetTipHash() []byte {
	return bc.tip
}

//创建区块链
func CreateBlockchain(address string) *Blockchain {
	var tip []byte
	db, err := bolt.Open(dbFile, 0600, nil)
	if err != nil {
		log.Panic(err)
	}
	err = db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		if b == nil {
			fmt.Println("No existing blockchain found. Creating a new one...")
			cbtx := NewCoinbaseTx(address, genesisCoinbaseData)
			genesis := NewGenesisBlock(cbtx)
			
			b, err := tx.CreateBucket([]byte(blocksBucket))
			if err != nil {
				log.Panic(err)
			}
			err = b.Put(genesis.BlockHash, genesis.Serialize())
			if err != nil {
				log.Panic(err)
			}
			err = b.Put([]byte(blocksBucket), genesis.BlockHash)
			if err != nil {
				log.Panic(err)
			}
			tip = genesis.BlockHash
		} else {
			tip = b.Get([]byte(blocksBucket))
		}

		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	bc := Blockchain{tip, db}
	return &bc
}

//往链中添加区块
func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {
	var lastHash []byte
	err := bc.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		lastHash = b.Get([]byte(blocksBucket))
		return nil
	})
	if err != nil {
		log.Panic(err)
	}

	if err != nil {
		log.Panic(err)
	}
	fmt.Println()
	//利用最后一块hash,挖掘一块新的区块
	newBlock := NewBlock(transactions, lastHash)
	fmt.Printf("\nMineBlock data :%x \nlast hash %x \n", newBlock.HashTransactions(), lastHash)

	//在挖掘新块之后，我们将其序列化表示保存到数据块中并更新"l"，该密钥现在存储新块的哈希。
	err = bc.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		fmt.Printf("\n put new block %x \n", newBlock.BlockHash)
		err := b.Put(newBlock.BlockHash, newBlock.Serialize())
		if err != nil {
			log.Panic(err)
		}
		err = b.Put([]byte(blocksBucket), newBlock.BlockHash)
		if err != nil {
			log.Panic(err)
		}
		bc.tip = newBlock.BlockHash
		return nil
	})
	return newBlock
}

//迭代器
func (bc *Blockchain) Iterator() *BlockchainIterator {
	bci := &BlockchainIterator{bc.tip, bc.db}
	return bci
}

func (bci *BlockchainIterator) GetCurrentHash() []byte {
	return bci.currentHash
}

//迭代下一区块
func (i *BlockchainIterator) Next() *Block {
	var block *Block
	err := i.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		encodeBlock := b.Get(i.currentHash)
		block = DeserializeBlock(encodeBlock)
		return nil
	})

	if err != nil {
		log.Panic(err)
	}
	i.currentHash = block.PrevBlockHash
	return block
}

func dbExists() bool {
	if _, err := os.Stat(dbFile); os.IsNotExist(err) {
		return false
	}
	return true
}

func NewBlockChain(address string) *Blockchain {
	if dbExists() == false {
		fmt.Println("No existing blockchain found. Create one first.")
		os.Exit(1)
	}
	var tip []byte
	db, err := bolt.Open(dbFile, 0600, nil)
	if err != nil {
		log.Panic(err)
	}
	err = db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		tip = b.Get([]byte(blocksBucket))
		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	bc := Blockchain{tip, db}
	return &bc
}
func (bc *Blockchain) Print() {
	bci := bc.Iterator()
	for {
		block := bci.Next()
		block.PrintBlock()
		//创世块是没有前一个区块的，所以PrevBlockHash的值是没有的
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
}

// FindUnspentTransactions returns a list of transactions containing unspent outputs
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
	var unspentTXs []Transaction
	spentTXOs := make(map[string][]int)
	bci := bc.Iterator()
	for {
		block := bci.Next()

		for _, tx := range block.Transactions {
			txID := hex.EncodeToString(tx.Id)
		Outputs:
			for outIdx, out := range tx.Vout {
				if spentTXOs[txID] != nil {
					//检查输出是否已经在输入中被引用
					for _, spentOut := range spentTXOs[txID] {
						if spentOut == outIdx {
							continue Outputs
						}
					}
				}
				if out.IsLockedWithKey(pubKeyHash) {
					unspentTXs = append(unspentTXs, *tx)
				}
			}

			if tx.IsCoinbase() == false {
				for _, in := range tx.Vin {
					if in.UseKey(pubKeyHash) {
						inTxId := hex.EncodeToString(in.TxId)
						spentTXOs[inTxId] = append(spentTXOs[inTxId], in.Vout)
					}
				}
			}

		}

		if len(block.PrevBlockHash) == 0 {
			break
		}
	}

	return unspentTXs
}

// FindSpendableOutputs finds and returns unspent outputs to reference in inputs
func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) {
	unspentOutputs := make(map[string][]int)
	unspentTXs := bc.FindUnspentTransactions(pubKeyHash)
	accumulated := 0

Work:
	for _, tx := range unspentTXs {
		txID := hex.EncodeToString(tx.Id)
		for outIdx, out := range tx.Vout {
			if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
				accumulated += out.Value
				unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

				if accumulated >= amount {
					break Work
				}
			}
		}
	}
	return accumulated, unspentOutputs
}

//查找并返回未花费的交易输出
func (bc *Blockchain) FindUTXO() map[string]TxOutputs {
	UTXO := make(map[string]TxOutputs)
	spentTXOs := make(map[string][]int)
	bci := bc.Iterator()

	for {
		block := bci.Next()

		for _, tx := range block.Transactions {
			txId := hex.EncodeToString(tx.Id)

		Outputs:
			for outIdx, out := range tx.Vout {
				//was the output spent
				if spentTXOs[txId] != nil {
					for _, spentOutIdx := range spentTXOs[txId] {
						if spentOutIdx == outIdx {
							continue Outputs
						}
					}
				}

				outs := UTXO[txId]
				outs.Outputs = append(outs.Outputs, out)
				UTXO[txId] = outs
			}

			if tx.IsCoinbase() == false {
				for _, in := range tx.Vin {
					inTxId := hex.EncodeToString(in.TxId)
					spentTXOs[inTxId] = append(spentTXOs[inTxId], in.Vout)
				}
			}
		}

		if len(block.PrevBlockHash) == 0 {
			break
		}

	}

	return UTXO
}

//关闭方法
func Close(bc *Blockchain) error {
	return bc.db.Close()
}

func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
	bci := bc.Iterator()
	for {
		block := bci.Next()
		for _, tx := range block.Transactions {
			if bytes.Compare(tx.Id, ID) == 0 {
				return *tx, nil
			}
		}
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
	return Transaction{}, errors.New("Transaction is not found")
}

func (bc *Blockchain) SignTransaction(tx *Transaction, prvKey ecdsa.PrivateKey) {
	prevTXs := make(map[string]Transaction)

	for _, vin := range tx.Vin {
		prevTX, err := bc.FindTransaction(vin.TxId)
		if err != nil {
			log.Panic(err)
		}
		prevTXs[hex.EncodeToString(prevTX.Id)] = prevTX
	}
	tx.Sign(prvKey, prevTXs)
}
